feat: register_resource with parent edge for agent_api_keys dual-write#253
Closed
dm36 wants to merge 1 commit into
Closed
feat: register_resource with parent edge for agent_api_keys dual-write#253dm36 wants to merge 1 commit into
dm36 wants to merge 1 commit into
Conversation
Closes the parent_agent cascade gap surfaced on scaleapi/agentex#354. The api_key dual-write (AGX1-272, PR #248) currently calls grant() which writes the owner edge in SpiceDB but NOT the parent_agent edge. The agent_api_key schema requires `read = ... & parent_agent->read & ...`, so every downstream read/update fails closed without that edge. This PR adds register_resource/deregister_resource (Port + adapter + service) and swaps the api_keys use case from grant→register_resource with parent=AgentexResource.agent(agent_id). Now the owner edge and parent_agent edge are written atomically. Stack: - scaleapi/scaleapi#144657 — sgp-authz 0.7.0 (parent_resource kwarg). - scaleapi/agentex#355 — agentex-auth Port + adapter + HTTP routes. - #248 — original AGX1-272 dual-write (this stacks on it). - THIS PR — extends #248 to use the parent-aware path. Changes: - Port: abstract register_resource(resource, parent=None) and deregister_resource(resource). - Adapter proxy: POST /v1/authz/register and /v1/authz/deregister. - Service: mirror existing grant/revoke pattern (principal_context override, _bypass support, parent in log line for cascade debugging). - Use case: swap grant→register_resource passing parent=agent; swap revoke→deregister_resource. except Exception wrappers preserved (fail-closed on register, best-effort on deregister). - Tests: rename mocks to register_resource/deregister_resource; assert the parent edge is passed correctly. Test plan: - pytest agentex/tests/integration/services/test_agent_api_key_service_dual_write.py → 8 / 8 pass. - New test ``test_create_api_key_calls_grant_when_flag_on`` asserts parent.type == AgentexResourceType.agent and parent.selector == agent.id. Other resource types' grant→register_resource swap is out of scope. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
5 tasks
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the `parent_agent` cascade gap surfaced on scaleapi/agentex#354. Stacked on PR #248 (AGX1-272 dual-write).
The api_key dual-write currently calls `authorization_service.grant(api_key)` which writes the owner edge in SpiceDB but NOT the `parent_agent` edge. The `agent_api_key` schema requires:
```
permission read = internal_effective_viewer & parent_agent->read & internal_tenant_gate
permission update = internal_effective_editor & parent_agent->update & internal_tenant_gate
```
Without the `parent_agent` edge, every downstream read/update fails closed — even for the owner.
This PR adds `register_resource` / `deregister_resource` methods and swaps the api_keys use case to write the parent edge atomically.
Dependency chain
This is PR #3 of 3 in a cross-repo coordinated change. Must merge in order:
Changes
5 files, +215/-66.
Test plan
```python
registered_parent = register.await_args.kwargs["parent"]
assert registered_parent.type == AgentexResourceType.agent
assert registered_parent.selector == agent.id
```
This is the contract that protects against silently dropping the parent edge in future changes.
Why this stacks on #248
The use-case change in `_register_api_key_in_spark_authz` directly depends on PR #248's existing code structure (the method, the feature flag check, the creator metadata population). Building on top of #248 makes the diff small and the intent obvious.
If #248 changes during review, this PR rebases mechanically. If the team prefers to fold the parent-edge work into #248 directly (one PR instead of two), happy to do that too.
Out of scope